package compile;

import Utils.CommandUtil;
import Utils.FileUtil;
import bean.Answer;
import bean.Question;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 张
 * Date: 2025-01-22
 * Time: 14:07
 */
// 每次的"编译 + 运行" 这个过程,就称为一个Task
public class Task {
    //通一组常量来约定临时文件的名字

    //所有临时文件所在的目录
    private String WORK_DIR = null;
    //约定代码的类名
    private String CLASS = null;
    //约定要编译的代码文件名
    private String CODE = null;
    //约定存放编译错误信息的文件名
    private String COMPILE_ERROR = null;
    //约定存放运行时的标准输出的文件名
    private String STDOUT = null;
    //约定存放运行时的标准错误的文件名
    private String STDERR = null;

    public Task() {
        //使用UUID来创建不同的临时文件
        WORK_DIR = "./tmp/" + UUID.randomUUID().toString() + "/";
        CLASS = "Solution";
        CODE = WORK_DIR + "Solution.java";
        COMPILE_ERROR = WORK_DIR + "compileError.txt";
        STDOUT = WORK_DIR + "stout.txt";
        STDERR = WORK_DIR + "stderr.txt";
    }

    //这个Task的核心方法就是compileAndRun(编译 和 运行)
    // 参数: 要编译运行的Java源代码
    // 返回值: 表示编译运行的结果. 编译出错/运行出错/运行正确...
    public Answer compileAndRun(Question question) {
        Answer answer = new Answer();
        //0.先准备好用来存放临时文件的tmp目录
        File workDir = new File(WORK_DIR);
        if(!workDir.exists()) {
            workDir.mkdirs();//创建多级目录
        }
        //进行安全性判定
        if(!checkCodeSafe(question.getCode())) {
            System.out.println("[Task] 用户提交了不安全的代码");
            answer.setError(3);
            answer.setReason("您提交的代码可能危害到服务器,禁止执行");
            return answer;
        }

        //1.先得将question.code写入到一个Solution.java的文件中
        FileUtil.writerFile(CODE,question.getCode());
        //2.创建子进程,调用javac进行编译(注:编译的时候,需要一个.java的文件,当前是通过String的方式提供的代码)
        //     如果编译出错,javac就会把错误信息写入到stderr里,使用专门的文件来保存编译错误 --- compileError.txt
        //  需要把编译命令给构造出来
        //第一个%s是放源代码所在的位置,第二个为生成文件的位置
        String compileCmd = String.format("javac -encoding utf8 %s -d %s",CODE,WORK_DIR);
        System.out.println("编译命令: " + compileCmd);
        CommandUtil.run(compileCmd,null,COMPILE_ERROR);
        //如果编译出错,错误信息就会被COMPILE_ERROR这个文件中
        String compileError = FileUtil.readFile(COMPILE_ERROR);
        if(!compileError.equals("")) {
            //编译出错 -- 直接返回Answer
            System.out.println("[compile.Task] 编译出错!");
            answer.setError(1);
            answer.setReason(compileError);
            return answer;
        }
        //3.创建子进程,调用java命令并执行
        //     运行程序的时候,也会把java子进程的标准输出和标准错误给获取到,stdout.txt / stderr.txt
        String rumCmd = String.format("java -classpath %s %s",WORK_DIR,CLASS);
        System.out.println("运行命令: " + rumCmd);
        CommandUtil.run(rumCmd,STDOUT,STDERR);
        String runError = FileUtil.readFile(STDERR);
        if(!runError.equals("")) {
            //运行时错误
            System.out.println("[compile.Task] 运行出错!");
            answer.setError(2);
            answer.setStderr(runError);
            return answer;
        }
        //4.父进程来获取编译执行的结果,并打包成Answer对象
        answer.setError(0);
        answer.setStdout(FileUtil.readFile(STDOUT));
        return answer;
    }



    private boolean checkCodeSafe(String code) {
        //代码黑名单
        List<String> blackList = new ArrayList<>();
        //防止提交的代码运行恶意程序
        blackList.add("Runtime");
        blackList.add("exec");
        //禁止提交的代码读写文件
        blackList.add("java.io");
        //禁止提交的代码访问网络
        blackList.add("java.net");

        for (String target : blackList) {
            int pos = code.indexOf(target);
            if(pos >= 0) {
                //找到任意的恶意代码就返回false,表示不安全
                return false;
            }
        }

        return true;

    }

    public static void main(String[] args) {
        Task task = new Task();
        Question question = new Question();
        question.setCode("public class Solution {\n" +
                "    public static void main(String[] args) {\n" +
                "        System.out.println(\"hello word\");\n" +
                "    }\n" +
                "}");
        Answer answer = task.compileAndRun(question);
        System.out.println(answer.toString());
    }
}
